﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Myotragus.Data.Util
{
	public static class ReflectionHelper
	{
		public static readonly Type[] EmptyTypes = new Type[0];
		public static readonly object[] EmptyObjects = new object[0];

		public static readonly Type TypeOfObject = typeof(object);
		public static readonly Type TypeOfVoid = typeof(void);
		public static readonly Type TypeOfString = typeof(string);

		public static void InitializeObject(object result, object prototype)
		{
			var lstResProps = GetProperties(result);
			var lstSrcProps = GetProperties(prototype).ToList();

			foreach (var prop in lstResProps)
			{
				var copyProp = prop;
				var matchingProp = lstSrcProps.Where(x => x.Name == copyProp.Name &&
					x.PropertyType == copyProp.PropertyType).FirstOrDefault();

				if (matchingProp == null)
					continue;

				var value = matchingProp.GetValue(prototype, EmptyObjects);

				if (EmptyObjects != null)
					prop.SetValue(result, value, EmptyObjects);
			}
		}

		public static Type GetDefaultInterface(Type type)
		{
			var interfaces = type.GetInterfaces().ToList();
			var result = interfaces.FirstOrDefault(t => IsDefault(type.Name, t.Name));
			return result ?? interfaces.OrderByDescending(t => t.GetInterfaces().Count()).First();
		}

		private static bool IsDefault(string typeName, string interfaceName)
		{
			if (interfaceName.StartsWith("I"))
				interfaceName = interfaceName.Substring(1);

			return typeName.EndsWith(interfaceName);
		}

		public static Type MakeGenericType<TTemplate>(params Type[] args)
		{
			return typeof(TTemplate).MakeGenericType(args);
		}

		public static Type MakeGenericType<TTemplate>(IEnumerable<Type> args)
		{
			return MakeGenericType<TTemplate>(args.ToArray());
		}

		public static IEnumerable<PropertyInfo> GetProperties(object obj)
		{
			return GetProperties(obj.GetType());
		}

		public static IEnumerable<PropertyInfo> GetProperties(Type typ)
		{
			return typ.GetProperties().Concat(typ.GetInterfaces().
				SelectMany(i => i.GetProperties())).Distinct(new PropertyInfoComparer());
		}

		public static MemberInfo ExtractMember(Expression expression)
		{
			var lambda = expression as LambdaExpression;
			if (lambda == null)
				throw new ArgumentNullException("expression");

			MemberExpression memberExpr = null;

			switch (lambda.Body.NodeType)
			{
				case ExpressionType.Convert:
					memberExpr =
						((UnaryExpression)lambda.Body).Operand as MemberExpression;
					break;
				case ExpressionType.MemberAccess:
					memberExpr = lambda.Body as MemberExpression;
					break;
			}

			if (memberExpr == null)
				throw new ArgumentException("expression");

			return memberExpr.Member;
		}
	}
}